package com.younio.local.service;

import com.alibaba.fastjson.JSON;
import com.younio.local.compnent.WebSocketServer;
import com.younio.local.entity.*;
import com.younio.local.repository.JbMapper;
import com.younio.local.util.Objects;
import com.younio.local.util.Result;
import com.younio.local.util.Sequence;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.younio.local.util.QueryParam.builder;

@Slf4j
@Service
@EnableScheduling
@SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"})
public class JbService {

    @Autowired
    private JbMapper jbMapper;

    @Autowired
    private WebSocketServer wsServer;

    @Autowired
    private Executor executor;

    @Autowired
    private RedisTemplate<String, Boolean> redisTemplate;

    private Symbol symbol;

    public JbService(Symbol symbol) {
        this.symbol = symbol;
        this.redisPrefix = symbol.getPcSign() + "_line:";
    }

    private String redisPrefix;
    private final ConcurrentMap<String, Integer> indexMap = new ConcurrentHashMap<>();

    /**
     * @desc 待处理的line暂存在这里
     * @author lan [1728209643@qq.com]
     * @create 2019/1/2 15:35
     */
    private final BlockingQueue<Line> lineQueue = new ArrayBlockingQueue<>(4);

    private String[] lines = {"1.", "2.", "3.", "4."};

    private final static AtomicBoolean initedIndexMap = new AtomicBoolean(Boolean.FALSE);

    public void remove(String uuid) {
        synchronized (lineQueue) {
            Optional<Line> any = lineQueue.stream().filter(line -> uuid.equals(line.getUuid())).findAny();
            Objects.checkArguments(any.isPresent(), "不存在该提交记录");
            lineQueue.remove(any.get());
        }
    }

    /**
     * @desc 把请求都放到lineQueue中, 校验(合法性校验 / 芯片是否初始化 / 所有芯片是否属于同一订单 / 该次检表记录是否已经保存) + 初始化
     * @author lan [1728209643@qq.com]
     * @create 2019/1/3 9:03
     */
    public void addTask(Line line) {
        Objects.requireNonNull(line, "参数错误");
        Objects.checkArguments(line.getNum() > 0, "参数错误");
        line.isInited();

        String line_ = line.getLine();
        while (!initedIndexMap.get()) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        synchronized (indexMap) {
            Objects.checkArguments(indexMap.containsKey(line_), "参数错误");
        }


        Boolean res = redisTemplate.opsForValue().get(redisPrefix + line.getUuid());
        if (res != null && res) {
            throw new RuntimeException("已完成绑定");
        }
        redisTemplate.opsForValue().set(redisPrefix + line.getUuid(), Boolean.FALSE);

        if (line.hasTid()) {
            checkTidList(line.getTiList());
        }
        synchronized (lineQueue) {
            lineQueue.forEach(l -> {
                Objects.checkArguments(!l.getUuid().equals(line.getUuid()), "已提交,请勿重复提交数据");
                Objects.checkArguments(!l.getLine().equals(line.getLine()), "已提交" + line.getLine().replace(".", "") + "线的数据,请稍后");
            });
            lineQueue.add(line);
        }
    }

    /**
     * @desc 重置indexMap中的记录
     * @author lan [1728209643@qq.com]
     * @create 2019/1/3 9:06
     */
    public synchronized void resetIndex() {
        synchronized (indexMap) {
            for (int i = 0; i < lines.length; i++) {
                //TODO      测试用
                indexMap.put(lines[i], Objects.defaultValue(jbMapper.maxId(lines[i]) - 100));
                log.info((i + 1) + "线最大索引为: " + indexMap.get(lines[i]));
            }
            initedIndexMap.set(Boolean.TRUE);
        }
    }

    /**
     * @desc 定时任务保存数据, 保存成功则广播通知
     * @author lan [1728209643@qq.com]
     * @create 2018/12/20 9:09
     */
    @Scheduled(cron = "* * * * * ?")
    public void tnmaSavedData() {
        synchronized (lineQueue) {
            List<Line> reslovedList = new LinkedList<>();
            lineQueue.forEach(line -> {
                try {
                    if (handler(line)) {
                        lineQueue.remove(line);
                        redisTemplate.opsForValue().set(redisPrefix + line.getUuid(), Boolean.TRUE);
                        wsServer.broadcast(JSON.toJSONString(Result.OK(line.getUuid())));
                        reslovedList.add(line);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    wsServer.broadcast(
                            JSON.toJSONString(
                                    Result.FAILED(
                                            builder()
                                                    .add("uuid", line.getUuid())
                                                    .add("errMsg", e.getLocalizedMessage()))));
                }
            });
            reslovedList.forEach(lineQueue::remove);
        }
    }

    /**
     * @desc 先从天马查询到新增加的检表记录    ->  初始化(计算Q值)   ->  设置tid   ->  resetIndex()
     * @author lan [1728209643@qq.com]
     * @create 2019/1/3 9:00
     */
    private boolean handler(Line line) {
        List<Jb> jbList;
        synchronized (indexMap) {
            jbList = getNew(line, indexMap.get(line.getLine()), line.getNum());
        }
        if (jbList == null) {
            return false;
        }
        line.isInited();
        jbList.forEach(Jb::init);
        //无芯片的设置一个虚拟tid seq+"#"
        jbList.forEach(jb -> jb.setTid(line.getTid(jb.getIndex())));

        if (save(jbList, line)) {
            resetIndex();
            executor.execute(new TbRunner(jbList));
        }
        return true;
    }

    /**
     * 查询数据库，绑定表号和matUuid
     */
    class TbRunner implements Runnable {

        private List<Jb> jbList;

        TbRunner(List<Jb> jbList) {
            this.jbList = jbList;
        }

        @Override
        public void run() {
            try {
                List<TbNo> tbNoList = new LinkedList<>();
                jbList.forEach(jb -> {
                    if (Objects.strIsNotEmpty(jb.getTb())) {
                        String prefix = TbNo.getPrefix(jb.getTb());
                        List<TbNo> tbNoListByPrefix = getTbNoListByPrefix(prefix);
                        if (tbNoListByPrefix != null) {
                            BigInteger val = new BigInteger(jb.getTb().replace(prefix, ""));
                            Optional<TbNo> optionalTbNo = tbNoListByPrefix.stream().filter(tbNo -> TbNo.match(tbNo, val)).findAny();
                            if (optionalTbNo.isPresent()) {
                                TbNo tbNo = optionalTbNo.get();
                                tbNo.setVal(val);
                                tbNo.setTbValue(jb.getTb());
                                tbNoList.add(tbNo);
                            }
                        }

                    }
                });
                save(tbNoList);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    @Transactional(rollbackFor = Exception.class)
    public boolean save(List<Jb> jbList, Line line) {
        jbMapper.saveResult(
                builder()
                        .add("jbList", jbList)
                        .add("line", line)
                        .add("hasTid", line.hasTid())
                        .add("sequence", Sequence.get())
                        .add("tnmaMac", symbol.getPcSign())
                        .add("operateTime", new Date())
        );
        return true;
    }


    /**
     * @param line 线
     * @param from 起始id
     * @param num  数量
     * @return java.util.List<com.younio.local.entity.Jb>   有正确数据时返回正确结果,无数据是返回null
     * @desc getNew 获取tnma保存的新数据
     * 先查询meter_item中是否有数据增加,得到meterIdList
     * 根据meterIdList查询完整的校表数据
     * 校验每一项的Q项是否相同,不相同即未保存完毕返回null
     * @author lan [1728209643@qq.com]
     * @create 2019/1/2 16:44
     */
    private List<Jb> getNew(Line line, int from, int num) {
        String line_ = line.getLine();
        List<Long> meterIdList = jbMapper.getNewMeterIdList(line_, from, num, line.getIndexList());
        if (meterIdList != null && !meterIdList.isEmpty() && meterIdList.size() == num) {
            List<Jb> jbList = jbMapper.findMore(meterIdList);
            if (jbList.size() == num) {
                Map<Integer, List<Jb>> map = jbList.stream().collect(
                        Collectors.groupingBy(jb -> jb.getQnList() == null ? -1 : jb.getQnList().size()));
                if (map.keySet().stream().distinct().count() == 1 && map.keySet().stream().findAny().get() != -1) {
                    return jbList;
                }
            }
        }
        return null;
    }

    /**
     * @desc 校验芯片是否校验/初始化, 是否属于同一订单
     * @author lan [1728209643@qq.com]
     * @create 2019/1/2 10:02
     */
    private void checkTidList(List<TI> tiList) {
        List<String> tidList = tiList.stream().map(TI::getTid).collect(Collectors.toList());

        try {
            Map<String, Integer> indexMap = tiList.stream().collect(
                    Collectors.toMap(TI::getTid, TI::getIndex));

            Map<String, List<String>> matMap = new HashMap<>();
            tidList.forEach(tid -> {
                TidInfo tidInfo = jbMapper.getTidInfo(tid);
                Objects.requireNonNull(tidInfo, "不存在该芯片/水表的校验信息");
                if (!tidInfo.isInit()) {
                    throw new RuntimeException("第" + indexMap.get(tid) + "只水表没有初始化");
                }
                Objects.checkArguments(Objects.strIsNotEmpty(tidInfo.getMatUuid()), "第" + indexMap.get(tid) + "只水表没有初始化");
                matMap.computeIfAbsent(tidInfo.getMatUuid(), k -> new LinkedList<>()).add(tid);
            });
            if (matMap.size() > 1) {
                StringBuilder err = new StringBuilder();
                matMap.values().forEach(list -> {
                    err.append("与");
                    err.append("[");
                    list.forEach(t -> err.append(indexMap.get(t)).append("、"));
                    err.replace(err.length() - 1, err.length(), "");
                    err.append("]");
                });
                throw new RuntimeException(err.toString().replaceFirst("与", "水表") + "不属于同一个订单");
            }
        } catch (IllegalStateException e) {
            String errMsg = e.getLocalizedMessage();
            if (errMsg != null) {
                if (errMsg.startsWith("Duplicate key ")) {
                    throw new RuntimeException(errMsg.replace("Duplicate key ", "有重复的TID: "));
                }
            }
        }
    }

    private void save(List<TbNo> tbNoList) {
        tbNoList = Objects.emptyToNull(tbNoList);
        if (tbNoList != null) {
            jbMapper.saveTbNoList(tbNoList);
        }
    }

    private List<TbNo> getTbNoListByPrefix(String prefix) {
        if (Objects.strIsNotEmpty(prefix)) {
            return Objects.emptyToNull(jbMapper.getTbNoListByPrefix(prefix));
        }

        return null;
    }


    private Object[] get3Q(String val, AtomicInteger fCount) {
        BigDecimal _val = new BigDecimal(val);
        _val = _val.multiply(new BigDecimal(100L));
        boolean f = _val.compareTo(BigDecimal.ZERO) < 0;
        String _valStr = _val.toString() + ".";
        if (f) {
            fCount.getAndIncrement();
            _valStr = _valStr.substring(1);
        }
        _valStr = _valStr.substring(0, _valStr.indexOf("."));
        if (_valStr.length() > 3) {
            return new Object[]{_valStr, "数据异常,数值较大"};
        } else if (_valStr.length() == 0) {
            return new Object[]{"000"};
        } else if (_valStr.length() == 1) {
            return new Object[]{"00" + _valStr};
        } else if (_valStr.length() == 2) {
            return new Object[]{"0" + _valStr};
        }
        return new Object[]{_valStr};
    }
}
